package io.cici.cc.mybatis.lite.builder.xml;

import io.cici.cc.mybatis.lite.builder.Builder;
import io.cici.cc.mybatis.lite.builder.BuilderException;
import io.cici.cc.mybatis.lite.builder.MapperBuilder;
import io.cici.cc.mybatis.lite.builder.ResultMapResolver;
import io.cici.cc.mybatis.lite.mapping.ParameterMapping;
import io.cici.cc.mybatis.lite.mapping.ResultMapping;
import io.cici.cc.mybatis.lite.mapping.ResultMappings;
import io.cici.cc.mybatis.lite.parse.Node;
import io.cici.cc.mybatis.lite.parse.XPathParser;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.type.TypeHandler;
import io.cici.cc.mybatis.lite.type.TypeHandlerRegistry;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class XmlMapperBuilder extends Builder {

    private final XPathParser xPathParser;
    private final MapperBuilder mapperBuilder;
    private final Map<String, Node> sqlFragments;
    private final String resource;

    @Deprecated
    public XmlMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, Node> sqlFragments, String namespace) {
        this(reader, configuration, resource, sqlFragments);
        this.mapperBuilder.setNamespace(namespace);
    }

    @Deprecated
    public XmlMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, Node> sqlFragments) {
        this(new XPathParser(reader, configuration.getVariables(), true, new XmlMapperEntityResolver()), configuration,
                resource, sqlFragments);
    }

    public XmlMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, Node> sqlFragments, String namespace) {
        this(inputStream, configuration, resource, sqlFragments);
        this.mapperBuilder.setNamespace(namespace);
    }

    public XmlMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, Node> sqlFragments) {
        this(new XPathParser(inputStream, configuration.getVariables(), true, new XmlMapperEntityResolver()), configuration, resource, sqlFragments);
    }

    private XmlMapperBuilder(XPathParser xPathParser, Configuration configuration, String resource, Map<String, Node> sqlFragments) {
        super(configuration);
        this.mapperBuilder = new MapperBuilder(configuration, resource);
        this.xPathParser = xPathParser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(xPathParser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);

        }

    }

    private void configurationElement(Node node) {
        try {
            String namespace = node.getStringAttribute("namespace");
            mapperBuilder.setNamespace(namespace);

            parameterMapElement(node.evalNodeList("/mapper/parameterMap"));
            resultMapElements(node.evalNodeList("/mapper/resultMap"));
            sqlElement(node.evalNodeList("/mapper/sql"));
            buildStatementFromContext(node.evalNodeList("select|insert|update|delete"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e,
                    e);
        }
    }


    private void parameterMapElement(List<Node> nodeList) {
        for (Node node : nodeList) {
            String id = node.getStringAttribute("id");
            String type = node.getStringAttribute("type");
            Class<?> parameterClass = null;
            try {
                parameterClass = Class.forName(type);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException(e);
            }
            List<Node> parameterNodeList = node.evalNodeList("parameter");
            List<ParameterMapping> parameterMappingList = new ArrayList<>();
            for (Node parameterNode : parameterNodeList) {
                String property = parameterNode.getStringAttribute("property");
                String type2 = parameterNode.getStringAttribute("type");
                TypeHandler typeHandler1 = TypeHandlerRegistry.getTypeHandler(type2);
                ParameterMapping parameterMapping = mapperBuilder.buildParameterMapping(parameterClass, property, type2, typeHandler1);
                parameterMappingList.add(parameterMapping);
            }
            mapperBuilder.addParameterMappings(id, parameterClass, parameterMappingList);
        }
    }

    private void resultMapElements(List<Node> nodeList) {
        for (Node node : nodeList) {
            resultMapElement(node);
        }
    }


    private ResultMappings resultMapElement(Node node) {
        String id = node.getStringAttribute("id");
        String type = node.getStringAttribute("type");
        Class<?> typeClass = null;
        try {
            typeClass = Class.forName(type);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }

        List<ResultMapping> resultMappings = new ArrayList<>();
        List<Node> childrenNodeList = node.getChildNodeList();
        for (Node childNode : childrenNodeList) {
            resultMappings.add(buildResultMappingFromContext(childNode));
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(mapperBuilder, id, typeClass, resultMappings);
        return resultMapResolver.resolve();
    }

    private ResultMapping buildResultMappingFromContext(Node node) {
        String property = node.getStringAttribute("property");
        String column = node.getStringAttribute("column");
        String type = node.getStringAttribute("type");
        TypeHandler typeHandler = null;
        return mapperBuilder.buildResultMapping(property, column, type, typeHandler);
    }

    private void sqlElement(List<Node> nodeList) {
        for (Node node : nodeList) {
            String id = node.getStringAttribute("id");
            sqlFragments.put(id, node);
        }
    }


    private void buildStatementFromContext(List<Node> nodeList) {
        for (Node node : nodeList) {
            final XmlStatementBuilder xmlStatementBuilder = new XmlStatementBuilder(configuration, mapperBuilder, node);
            xmlStatementBuilder.parseStatementNode();
        }
    }


}
